from functools import reduce, partial
from operator import add, mul, itemgetter, attrgetter, methodcaller
from bingocall import BingoCage
from clip import clip, clip_annot
from inspect import signature
from collections import namedtuple
import operator
import unicodedata


# 1、把函数视作对象
def factorial(n):
    """returns n!"""
    return 1 if n < 2 else n * factorial(n - 1)


print(factorial(42))
print(factorial.__doc__)
print(type(factorial))
print(help(factorial))  # from __doc__
fact = factorial
print(fact)
print(fact(5))
# map 函数返回一个可迭代对象，里面的元素是把第一个参数（一个函数）应用到第二个参数（一个可迭代对象，这里是range(11)）中各个元素上得到的结果。
print(map(factorial, range(11)))
print(list(map(fact, range(11))))

# 2、高阶函数 接受函数为参数，或者把函数作为结果返回的函数是高阶函数
fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana']
print(sorted(fruits, key=len))


def reverse(word):
    return word[::-1]


print(reverse('testing'))
# 创建押韵词典
print(sorted(fruits, key=reverse))

# map filter reduce 替代品
print(list(map(fact, range(6))))
print([fact(n) for n in range(6)])
print(list(map(factorial, filter(lambda x: x % 2, range(6)))))
print([factorial(n) for n in range(6) if n % 2])
# reduce -> sum
print(reduce(add, range(100)))
print(sum(range(100)))
# 3、匿名函数 lambda
print(sorted(fruits, key=lambda x: x[::-1]))
# 4、可调用对象 callable()判断对象是否能调用
print(abs, str, 3)
print([callable(obj) for obj in [abs, str, 3]])
# 5、用户定义的可调用类型 @see bingocall.py
bingo = BingoCage(range(3))
print(bingo.pick())
print(bingo())  # 实例和函数一样调用 因为实现了__call__()方法
print(callable(bingo))

# 6、函数内省
print(dir(factorial))


class C:
    pass


obj = C()


def func(): pass


# 函数有对象没有的属性
# __annotations__ 参数和返回值的注解
# __call__ 实现()运算符 即可调用对象协议
# __closure__ 函数闭包，即自由变量的绑定（通常是None）
# __code__ 编译成字节码的函数元数据和函数定义体
# __defaults__ 形式参数的默认值  __kwdefaults__ 仅限关键字形式参数的默认值
# __get__ 实现制度描述符协议
# __globals__ 函数所在模块中的全局变量
# __name__ 函数名称  __qualname__ 函数的限定名称
diff = sorted(set(dir(func)) - set(dir(obj)))
print(diff)


# 7、从定位参数到仅限关键字参数
def tag(name, *content, cls=None, **attrs):
    """生成一个或多个HTML标签"""
    if cls is not None:
        attrs['class'] = cls
    if attrs:
        attr_str = ''.join(' %s="%s"' % (attr, value) for attr, value in sorted(attrs.items()))
    else:
        attr_str = ''
    if content:
        return "\n".join('<%s%s>%s<%s>' % (name, attr_str, c, name) for c in content)
    else:
        return '<%s%s/>' % (name, attr_str)


print(tag('br'))
print(tag('p', 'hello', 'world'))
print(tag('p', 'hello', id=33))
print(tag('p', 'hello', 'world', cls='sidebar'))
print(tag(content="testing", name='img'))
my_tag = {'name': 'img', 'title': 'Sunset Boulevard', 'src': 'sunset.jpg', 'cls': 'framed'}
print(tag(**my_tag))  # ** 字典所有元素作为单个参数传入


# 一定要仅限关键词参数
def f(a, *, b):
    return a, b


print(f(1, b=2))

# 8、获取关于参数的信息
print(clip.__defaults__)
print(clip.__code__)
print(clip.__code__.co_varnames)
print(clip.__code__.co_argcount)
sig = signature(clip)  # inspect.Signature 个有序映射，把参数名和 inspect.Parameter 对象对应起来
print(sig)
# param 属性 name,kind,default,annotation
for name, param in sig.parameters.items():
    print(param.kind, ":", name, '=', param.default)
# Signature bind
sig = signature(tag)
bound_args = sig.bind(**my_tag)
print(bound_args)
for name, value in bound_args.arguments.items():
    print(name, '=', value)
del my_tag['name']
# bound_args= sig.bind(**my_tag) #TypeError: missing a required argument: 'name'

# 9、函数注解 python仅仅 把注解存储到__annotations__里
print(clip_annot.__annotations__)
sig = signature(clip_annot)
print(sig.return_annotation)
for param in sig.parameters.values():
    note = repr(param.annotation).ljust(13)
    print(note, ':', param.name, "=", param.default)


# 10、支持函数式编程的包
# operator模块
def fact(n):
    """阶乘"""
    return reduce(lambda a, b: a * b, range(1, (n + 1)))


def fact_pro(n):
    """operator.mul 代替lambada表达式"""
    return reduce(mul, range(1, (n + 1)))


metro_data = [('Tokyo', 'JP', 36.933, (35.689722, 139.691667)),
              ('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)),
              ('Mexico City', 'MX', 20.142, (19.433333, -99.133333)),
              ('New York-Newark', 'US', 20.104, (40.808611, -74.020386)),
              ('Sao Paulo', 'BR', 19.649, (-23.547778, -46.635833)), ]
# itemgetter 自行构建函数
# 按照元组第二个值排序 lambada fields:fields[1]
for city in sorted(metro_data, key=itemgetter(1)):
    print(city)
cc_name = itemgetter(1, 0)
for city in metro_data:
    print(cc_name(city))
# attrgetter
LatLong = namedtuple('LatLong', 'lat long')
Metropolis = namedtuple('Metropolis', 'name cc pop coord')
metro_areas = [Metropolis(name, cc, pop, LatLong(lat, long))
               for name, cc, pop, (lat, long) in metro_data]
print(metro_areas[0])
print(metro_areas[0].coord.lat)
name_lat = attrgetter('name', 'coord.lat')
for city in sorted(metro_areas, key=attrgetter('coord.lat')):
    print(name_lat(city))
# operator 定义的函数
print([name for name in dir(operator) if not name.startswith('_')])
# methodcaller 创建的函数会在对象上调用参数指定的方法
s = 'The time has come'
upcase = methodcaller('upper')
print(upcase(s))
hiphenate = methodcaller('replace', ' ', '_')
print(hiphenate(s))
print(str.upper(s))
# functools  ->reduce partial partialmethod
# functools.partial 部分应用一个函数，基于一个函数创建一个新的可调用对象，把原函数的某些参数固定
triple = partial(mul, 3)  # 把第一个定位参数定为 3
print(triple(7))
print(list(map(triple, range(1, 10))))
# 便利的Unicode规范化函数
nfc = partial(unicodedata.normalize, 'NFC')
s1 = 'café'
s2 = 'cafe\u0301'
print((s1, s2))
print(s1 == s2)
print(nfc(s1) == nfc(s2))
# partial tag method
print(tag)
picture = partial(tag, 'img', cls='pic-frame')
print(picture(src='wumpus.jpeg'))
print(picture)
print(picture.func)  # functools.partial 对象提供了访问原函数和固定参数的属性
print(picture.args)
print(picture.keywords)
